package net.oschina.renyong.query.generator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

import net.oschina.renyong.query.generator.constant.FunctionReturnTypeEnum;
import net.oschina.renyong.query.generator.exception.QueryParseException;
import net.oschina.renyong.query.generator.po.ContentField;
import net.oschina.renyong.query.generator.util.QuerySqlUtil;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.*;

/**
 * 查询SQL解析器
 * @author Timothy
 */
public class QuerySqlParser {

	/**
	 * 解析查询语句获取查询字段集合
	 * @param querySql 查询语句
	 * @param queryConfigName 查询语句对应的配置名
	 * @param namingStrategy 命名策略
	 * @param functionReturnTypeEnumListMap 函数返回值类型MAP
	 * @param isSelect 解析获取的是否为select字段
	 * @return 查询字段信息的集合
	 */
	public static List<ContentField> parseGetContentFieldList(String querySql, String queryConfigName,
			NamingStrategy namingStrategy, Map<FunctionReturnTypeEnum, List<String>> functionReturnTypeEnumListMap,
			boolean isSelect) throws JSQLParserException {
		// 解析查询语句
		Select select = (Select) CCJSqlParserUtil.parse(querySql);
		PlainSelect plainSelect = (PlainSelect) select.getSelectBody();

		List<ContentField> contentFieldList = new ArrayList<>();
		// 获取 表别名-表实际名 映射
		Map<String, String> tblNameByAliasMap = parseGetTblNameByAliasMap(plainSelect);
		List<SelectItem> itemList = plainSelect.getSelectItems();
		for (SelectItem item : itemList) {
			ContentField contentField = new ContentField();

			SelectExpressionItem selectExpressionItem = (SelectExpressionItem) item;
			// 获取字段的属性名
			String propertyName = QuerySqlUtil.processUnderlineName(selectExpressionItem.getAlias().getName(),
					namingStrategy);
			contentField.setAliasName(selectExpressionItem.getAlias().getName());
			contentField.setPropertyName(propertyName);
			contentField.setSelect(isSelect);

			if (selectExpressionItem.getExpression() instanceof NullValue) {
				// select的字段是null，这个已经不推荐使用了
				contentField.setTableName(null);
				contentField.setColumnName(selectExpressionItem.getAlias().getName());
				contentField.setColumnFullName(null);
				contentField.setColumnType(DbColumnType.STRING);
			} else if (selectExpressionItem.getExpression() instanceof Function) {
				// select的字段是个函数
				Function function = (Function) selectExpressionItem.getExpression();
				if (functionReturnTypeEnumListMap.get(FunctionReturnTypeEnum.INTEGER) != null
						&& functionReturnTypeEnumListMap.get(FunctionReturnTypeEnum.INTEGER)
								.contains(function.getName().toLowerCase())) {
					// 是整数型函数
					contentField.setTableName(null);
					contentField.setColumnName(selectExpressionItem.getAlias().getName());
					contentField.setColumnFullName((selectExpressionItem.getExpression()).toString());
					contentField.setColumnType(DbColumnType.INTEGER);
				} else if (functionReturnTypeEnumListMap.get(FunctionReturnTypeEnum.STRING) != null
						&& functionReturnTypeEnumListMap.get(FunctionReturnTypeEnum.STRING)
						.contains(function.getName().toLowerCase())) {
					// 是字符串型函数
					contentField.setTableName(null);
					contentField.setColumnName(selectExpressionItem.getAlias().getName());
					contentField.setColumnFullName((selectExpressionItem.getExpression()).toString());
					contentField.setColumnType(DbColumnType.STRING);
				} else {
					throw new RuntimeException("未知的数据类型！" + function.getName());
				}
			} else if (selectExpressionItem.getExpression() instanceof Column) {
				// select的字段是表的列
				Column column = (Column) selectExpressionItem.getExpression();
				// 获取真实表名
				String tblName = tblNameByAliasMap.get(getTableFullName(column.getTable()));
				String columnName = column.getColumnName();
				if (tblName == null) {
					throw new QueryParseException(
							String.format("关联查询 %s 解析失败，字段 %s 没有找到对应的表名", queryConfigName, columnName));
				}
				contentField.setTableName(tblName);
				contentField.setColumnName(columnName);
				contentField.setColumnFullName(column.toString());
			} else {
				// select的字段是其它情况
				System.out.println("一个特殊的字段类型，当string处理！ 类型：" + selectExpressionItem.getExpression().toString());
				// 其余类型目前都返回字符串
				contentField.setTableName(null);
				contentField.setColumnName(selectExpressionItem.getAlias().getName());
				contentField.setColumnFullName(null);
				contentField.setColumnType(DbColumnType.STRING);
			}
			contentFieldList.add(contentField);
		}
		return contentFieldList;
	}

	/**
	 * 解析获取非select字段的字段信息集合
	 * @param querySql 查询语句
	 * @param queryConfigName 查询语句对应的配置名
	 * @param namingStrategy 命名策略
	 * @param functionReturnTypeEnumListMap 函数返回值类型MAP
	 * @param noSelectField 非查询字段
	 * @return 非select字段的字段内容集合
	 * @throws JSQLParserException
	 */
	public static List<ContentField> parseGetNoSelectFieldList(String querySql, String queryConfigName,
			NamingStrategy namingStrategy, Map<FunctionReturnTypeEnum, List<String>> functionReturnTypeEnumListMap,
			String noSelectField) throws JSQLParserException {
		// 解析查询语句
		Select select = (Select) CCJSqlParserUtil.parse(querySql);
		PlainSelect plainSelect = (PlainSelect) select.getSelectBody();

		// 取得 表别名-表实际名 MAP
		Map<String, String> tblNameByAliasMap = parseGetTblNameByAliasMap(plainSelect);
		// 拼出以非查询字段作为select的查询语句，这样就可以获取非查询字段的字段信息集合
		String noSelectFieldSql = "select " + noSelectField + " from " + getNoSelectFromStr(tblNameByAliasMap);
		return parseGetContentFieldList(noSelectFieldSql, queryConfigName, namingStrategy,
				functionReturnTypeEnumListMap, false);
	}

	/**
	 * 解析获取表的 别名-表名 映射
	 * @param plainSelect PlainSelect
	 * @return 别名-表名映射
	 */
	private static Map<String, String> parseGetTblNameByAliasMap(PlainSelect plainSelect) {
		if (plainSelect.getFromItem() instanceof SubJoin) {
			return parseSubJoinGetTblNameByAliasMap((SubJoin) plainSelect.getFromItem());
		}

		// 下面是直接取表
		// 初始化别名-表名映射
		Map<String, String> tblNameByAliasMap = new HashMap<>();
		Table table = (Table) plainSelect.getFromItem();
		tblNameByAliasMap.put(table.getAlias().getName(), getTableFullName(table));
		if (plainSelect.getJoins() != null) {
			for (Join join : plainSelect.getJoins()) {
				table = (Table) join.getRightItem();
				tblNameByAliasMap.put(table.getAlias().getName(), getTableFullName(table));
			}
		}
		return tblNameByAliasMap;
	}

	private static Map<String, String> parseSubJoinGetTblNameByAliasMap(SubJoin subJoin) {
		Map<String, String> tblNameByAliasMap = new HashMap<>();
		if (subJoin.getLeft() instanceof SubJoin) {
			tblNameByAliasMap.putAll(parseSubJoinGetTblNameByAliasMap((SubJoin) subJoin.getLeft()));
		} else if (subJoin.getLeft() instanceof Table) {
			Table table = (Table) subJoin.getLeft();
			tblNameByAliasMap.put(table.getAlias().getName(), getTableFullName(table));
		} else {
			throw new QueryParseException("解析SubJoin出错！subJoin.left=" + subJoin.getLeft());
		}
		if (subJoin.getJoinList() != null) {
			for (Join join : subJoin.getJoinList()) {
				Table table = (Table) join.getRightItem();
				tblNameByAliasMap.put(table.getAlias().getName(), getTableFullName(table));
			}
		}
		return tblNameByAliasMap;
	}

	private static String getTableFullName(Table table) {
		return table.getSchemaName() != null ? table.getSchemaName() + "." + table.getName() : table.getName();
	}

	private static String getNoSelectFromStr(Map<String, String> tblNameByAliasMap) {
		StringBuilder sb = new StringBuilder();
		for (String key : tblNameByAliasMap.keySet()) {
			if (sb.length() > 1) {
				sb.append(", ");
			}
			sb.append(tblNameByAliasMap.get(key)).append(" ").append(key);
		}
		return sb.toString();
	}
}
